using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;

namespace DejaVuTocReader
{
    
        /**
     * This class represents an index structure for bundled and indexed documents.
     *
     * @author Bill C. Riemers
     * @version $Revision: 1.4 $
     */
    public class DjVmDir
    {
      //~ Static fields/initializers ---------------------------------------------

      /** The version of this class. */
      public static int version = 1;

      //~ Instance fields --------------------------------------------------------

      private  Uri initURL = null;
        
        
      private  Hashtable id2file = new Hashtable();
      private  Hashtable name2file = new Hashtable();
      private  Hashtable title2file = new Hashtable();
      private  ArrayList files_list = new ArrayList();
      private  ArrayList page2file = new ArrayList();
        

      //~ Constructors -----------------------------------------------------------

      /**
       * Creates a new DjVmDir object.
       */
      public DjVmDir() {}

      //~ Methods ----------------------------------------------------------------

      
      /**
       * Query if this is image data.
       *
       * @return false
       */
      public bool isImageData()
      { 
          return false;
      }
      
      /**
       * Set the top level URL used to initialize this document.
       *
       * @param url DOCUMENT ME!
       */
      public void setInitURL( Uri url)
      {
        initURL = url;
      }

      /**
       * Get the top level URL used to initialize this document.
       *
       * @return the top level URL
       */
      public Uri getInitURL()
      {
        return initURL;
      }

      /**
       * Convert a relative url to a page number. The first page number is page
       * 0.
       *
       * @param url The URL to convert
       *
       * @return the page number if found, otherwise -1
       */
      public int getPageno( String url)
      {
        int retval = -1;

        if((url != null) && (url.Length > 0))
        {
          if(url[0] == '#')
          {
             String name = url.Substring(1);

            try
            {
              File file = id_to_file(name);

              if(file == null)
              {
                file = name_to_file(name);

                if(file == null)
                {
                  file = title_to_file(name);
                }
              }

              retval =
                (file != null)
                ? file.get_page_num()
                : (int.Parse(name) - 1);
            }
            catch(Exception)
            {
                // ignored
            }
          }
        }

        return retval;
      }

      /**
       * Create a copy by value of this instance.  Note that this method assumes this class is .
       *
       * @return the newly created copy
       */
      public Object clone()
      {
        DjVmDir retval = new DjVmDir();

        
        retval.setInitURL(getInitURL());

    //      xretval.setInitURL(getInitURL());
          
        for(IEnumerator e = get_files_list().GetEnumerator(); e.MoveNext();)
        {
          try
          {
            retval.insert_file((File)((File)e.Current).clone());
          }
          catch(IOException) {}
        }
        return retval;
      }

      /**
       * Create a new instance of DjVmDir.File
       *
       * @return the new instance of DjVmDir.File
       */
      public File createFile()
      {
        return new File();
      }

      /**
       * Create a new instance of DjVmDir.File
       *
       * @param load_name the name used for loading
       * @param save_name the name used for saving
       * @param title the title
       * @param file_type the type of record
       *
       * @return the new instance of DjVmDir.File
       */
      public File createFile(
         String load_name,
         String save_name,
         String title,
         int    file_type)
      {
        return new File(load_name, save_name, title, file_type);
      }

      /**
       * Called to decode the data source and initialize this index.
       *
       * @param pool data source
       *
       * @throws IOException if an error occurs
       */
      public void decode( CachedInputStream pool)
      {
         CachedInputStream str = (CachedInputStream)pool.clone();
        files_list.Capacity = 0;
        page2file.Capacity = 0;
        name2file.Clear();
        id2file.Clear();
        title2file.Clear();

        int     ver     = str.read();
        bool bundled = (ver & 0x80) != 0;
        ver &= 0x7f;

        if(ver > version)
        {
          throw new IOException("DjVmDir.version_error " + version + " " + ver);
        }

        int files = str.read();
        files = (files << 8) | str.read();

        if(files > 0)
        {
          for(int i = 0; i < files; i++)
          {
             File file = createFile();
            files_list.Add(file);

            if(bundled)
            {
              int b = str.read();
              b             = (b << 8) | str.read();
              b             = (b << 8) | str.read();
              file.offset   = (b << 8) | str.read();

              if(ver == 0)
              {
                b           = str.read();
                b           = (b << 8) | str.read();
                file.size   = (b << 8) | str.read();
              }

              if(file.offset == 0)
              {
                throw new IOException("DjVmDir.no_indirect");
              }
            }
            else
            {
              file.offset = file.size = 0;
            }
          }

           BSInputStream bs_str = new BSInputStream(str);

          if(ver > 0)
          {
            for(int i = 0; i < files_list.Count; i++)
            {
               File file = (File)files_list[i];
              int        b = bs_str.read();
              b           = (b << 8) | bs_str.read();
              b           = (b << 8) | bs_str.read();
              file.size   = b;
            }
          }

          for(int i = 0; i < files_list.Count; i++)
          {
             File file = (File)files_list[i];
            file.flags = (byte)bs_str.read();
          }

          if(ver == 0)
          {
            for(int i = 0; i < files_list.Count; i++)
            {
               File file    = (File)files_list[i];
               byte flags_0 = file.flags;
              byte       flags_1 =
                ((flags_0 & File.IS_PAGE_0) != 0)
                ? (File.PAGE)
                : (File.INCLUDE);

              if((flags_0 & File.HAS_NAME_0) != 0)
              {
                flags_1 |= File.HAS_NAME;
              }

              if((flags_0 & File.HAS_TITLE_0) != 0)
              {
                flags_1 |= File.HAS_TITLE;
              }

              file.flags = flags_1;
            }
          }

          if(files_list.Count > 0)
          {
            MemoryStream bout   = new MemoryStream();
            byte[]       buffer = new byte[1024];

            for(int length; (length = bs_str.read(buffer)) > 0;)
            {
              bout.Write(buffer, 0, length);
            }

            buffer = bout.ToArray();

            ArrayList stringList  = new ArrayList();
            int    startOffset = 0;
            int    endOffset   = 0;

            for(; endOffset < buffer.Length; ++endOffset)
            {
              if(buffer[endOffset] == 0)
              {
                
                  string s = CreateString(buffer,startOffset,endOffset - startOffset);
                 
                stringList.Add(s);
                startOffset = endOffset + 1;
              }
            }

            if(startOffset < endOffset)
            {
              stringList.Add(
                CreateString(buffer, startOffset, endOffset - startOffset));
            }

            // Copy names into the files
            for(int i = 0, stringNo = 0; i < files_list.Count; i++)
            {
               File file = (File)files_list[i];
              file.id = (String)stringList[stringNo++];

              if((file.flags & File.HAS_NAME) != 0)
              {
                file.name = (String)stringList[stringNo++];
              }
              else
              {
                file.name = file.id;
              }

              if((file.flags & File.HAS_TITLE) != 0)
              {
                file.name = (String)stringList[stringNo++];
              }
              else
              {
                file.title = file.id;
              }
            }
          }

          // Check that there is only one file with SHARED_ANNO flag on
          int shared_anno_cnt = 0;

          for(int i = 0; i < files_list.Count; i++)
          {
             File file = (File)files_list[i];

            if(file.is_shared_anno())
            {
              shared_anno_cnt++;
            }
          }

          if(shared_anno_cnt > 1)
          {
            throw new IOException("DjVmDir.corrupt");
          }

          // Now generate page=>file array for direct access
          int pages = 0;

          for(int i = 0; i < files_list.Count; i++)
          {
             File file = (File)files_list[i];

            if(file.is_page())
            {
              pages++;
            }
          }

          page2file.Capacity = 0;

          for(int i = 0; i < files_list.Count; i++)
          {
             File file = (File)files_list[i];

            if(file.is_page())
            {
              file.page_num = page2file.Count;
              page2file.Add(file);
            }
          }

          // Generate name2file map
          for(int i = 0; i < files_list.Count; i++)
          {
             File file = (File)files_list[i];

            if(name2file.Contains(file.name))
            {
              throw new IOException("DjVmDir.dupl_name " + file.name);
            }

            name2file.Add(file.name, file);
          }

          // Generate id2file map
          for(int i = 0; i < files_list.Count; i++)
          {
             File file = (File)files_list[i];

            if(id2file.Contains(file.id))
            {
              throw new IOException("DjVmDir.dupl_id " + file.id);
            }

            id2file.Add(file.id, file);
          }

          // Generate title2file map
          for(int i = 0; i < files_list.Count; i++)
          {
             File file = (File)files_list[i];

            if((file.title != null) && (file.title.Length  > 0))
            {
              if(title2file.Contains(file.title))
              {
                throw new IOException("DjVmDir.dupl_title " + file.title);
              }

              title2file.Add(file.title, file);
            }
          }
        }
      }

        public static string CreateString(byte[] buffer,int startOffset,int length)
        {
            //String s = new String(buffer, startOffset, endOffset - startOffset);
                
              byte[]  buffer2 = new byte[length];
              Array.Copy(buffer,startOffset,buffer2,0,length);
              MemoryStream ms = new MemoryStream(buffer2);
            StreamReader sr = new StreamReader(ms,Encoding.UTF8);
              return sr.ReadToEnd();
        }

      /**
       * Called to remove the named file from the index.
       *
       * @param id the name of the file to remove
       */
      public void delete_file( String id)
      {
        for(int i = 0; i < files_list.Count; i++)
        {
          File f = (File)files_list[i];

          if(id.Equals(f.id))
          {
            name2file.Remove(f.name);
            id2file.Remove(f.id);
            title2file.Remove(f.title);

            if(f.is_page())
            {
              for(int page = 0; page < page2file.Count; page++)
              {
                if(f.Equals(page2file[page]))
                {
                  page2file.RemoveAt(page);

                  for(; page < page2file.Count; page++)
                  {
                    File xfile = (File)page2file[page];
                    xfile.page_num = page;
                  }

                  break;
                }
              }
            }

            files_list.RemoveAt(i);

            break;
          }
        }
      }

      /**
       * Called to find the position of a File in the list
       *
       * @param f the file to search for
       *
       * @return the position of the file or -1
       */
      public int get_file_pos( File f)
      {
        for(int i = 0; i < files_list.Count; i++)
        {
          if(f.Equals(files_list[i]))
          {
            return i;
          }
        }

        return -1;
      }

      /**
       * Query the file list.
       *
       * @return the vector of DjVmDir.File objects
       */
      public ArrayList get_files_list()
      {
        return files_list;
      }

      /**
       * Query the size of the file list
       *
       * @return the size of the file list
       */
      public int get_files_num()
      {
        return files_list.Count;
      }

      /**
       * Query the position of a page in the list
       *
       * @param page_num page number to search for
       *
       * @return the list position or -1
       */
      public int get_page_pos(int page_num)
      {
        File file = page_to_file(page_num);

        return (file != null)
        ? get_file_pos(file)
        : (-1);
      }

      /**
       * Query the number of pages in the index.
       *
       * @return the number of pages in the index
       */
      public int get_pages_num()
      {
        return page2file.Count;
      }

      /**
       * Query the document shared annotation
       *
       * @return the document shared annotation
       */
      public File get_shared_anno_file()
      {
        File retval = null;

        for(int i = 0; i < files_list.Count; i++)
        {
          File frec = (File)files_list[i];

          if(frec.is_shared_anno())
          {
            retval = frec;

            break;
          }
        }

        return retval;
      }

      /**
       * Called to map an id to a File.
       *
       * @param id the load name to look for
       *
       * @return the DjVmDir.File record
       */
      public File id_to_file( String id)
      {
        return (File)id2file[id];
      }

      /**
       * Called to add a file to the index.
       *
       * @param file DjVmDir.File object to add
       *
       * @return the position where the File was added
       *
       * @throws IOException if an error occurs
       */
      public int insert_file( File file)
      {
        return insert_file(file, -1);
      }

      /**
       * Called to add a file to the index.
       *
       * @param file DjVmDir.File object to add
       * @param pos_num the position to insert the file
       *
       * @return the position where the File was added
       *
       * @throws IOException if an error occurs
       */
      public int insert_file(
         File file,
        int   pos_num)
      {
        if(pos_num < 0)
        {
          pos_num = files_list.Count;
        }

        // Modify maps
        if(id2file.Contains(file.id))
        {
          throw new IOException("DjVmDir.dupl_id2 " + file.id);
        }

        if(name2file.Contains(file.name))
        {
          throw new IOException("DjVmDir.dupl_name2 " + file.name);
        }

        name2file.Add(file.name, file);
        id2file.Add(file.id, file);

        if(file.title.Length > 0)
        {
          if(title2file.Contains(file.title))
          {
            throw new IOException("DjVmDir.dupl_title2 " + file.title);
          }

          title2file.Add(file.title, file);
        }

        // Make sure that there is no more than one file with shared annotations
        if(file.is_shared_anno())
        {
          for(int i = 0; i < files_list.Count; i++)
          {
            File xfile = (File)files_list[i];

            if(file.is_shared_anno())
            {
              throw new IOException("DjVmDir.multi_save2");
            }
          }
        }

        // Add the file to the list
        files_list.Insert(pos_num,file);

        if(file.is_page())
        {
          // This file is also a page
          // Count its number
          int page_num = 0;

          for(int i = 0; i < files_list.Count; i++)
          {
            File xfile = (File)files_list[i];

            if(xfile.Equals(file))
            {
              break;
            }

            if(xfile.is_page())
            {
              page_num++;
            }
          }

          page2file.Insert(page_num,file);

          for(int i = page_num; i < page2file.Count; i++)
          {
            File xfile = (File)page2file[i];
            xfile.page_num = i;
          }
        }

        return pos_num;
      }

      /**
       * Query if the index is for a bundled document.
       *
       * @return true if bundled 
       */
      public bool is_bundled()
      {
        return !is_indirect();
      }

      /**
       * Query if the index is for an indirect document.
       *
       * @return true if indirect 
       */
      public bool is_indirect()
      {
        return ((files_list.Count > 0) && (files_list[0] != null)
        && (((File)files_list[0]).offset == 0));
      }

      /**
       * Query the File mapped to name.
       *
       * @param name the name to look for
       *
       * @return the File or null
       */
      public File name_to_file( String name)
      {
        return (File)name2file[name];
      }

      /**
       * Query the File mapped to the specified page number.
       *
       * @param page_num the page number to look for
       *
       * @return the File or null
       */
      public File page_to_file(int page_num)
      {
        return (File)((page_num < page2file.Count)
        ? page2file[page_num]
        : null);
      }

      /**
       * Set the filename corresponding to an id.
       *
       * @param id the id to map
       * @param name the name to map
       *
       * @throws IOException if an error occurs
       */
      public void set_file_name(
         String id,
         String name)
      {
        // First see, if the name is unique
        for(int i = 0; i < files_list.Count; i++)
        {
          File file = (File)files_list[i];

          if(!id.Equals(file.id) && name.Equals(file.name))
          {
            throw new IOException("DjVmDir.name_in_use " + name);
          }
        }

        File fileID = (File)id2file[id];

        // Check if ID is valid
        if(fileID == null)
        {
          throw new IOException("DjVmDir.no_info " + id);
        }

        name2file.Remove(fileID.name);
        fileID.name = name;
        name2file.Add(name, fileID);
      }

      /**
       * Set the title corresponding to an id.
       *
       * @param id the id to map
       * @param title the title to map to
       *
       * @throws IOException if an error occurs
       */
      public void set_file_title(
         String id,
         String title)
      {
        for(int i = 0; i < files_list.Count; i++)
        {
          File file = (File)files_list[i];

          if(!id.Equals(file.id) && title.Equals(file.title))
          {
            throw new IOException("DjVmDir.title_in_use " + title);
          }
        }

        // Check if ID is valid
        File fileID = (File)id2file[id];

        if(fileID == null)
        {
          throw new IOException("DjVmDir.no_info " + id);
        }

        title2file.Remove(fileID.title);
        fileID.title = title;
        title2file.Add(title, fileID);
      }

      /**
       * Find the DjVmDir.File with the specified title.
       *
       * @param title the title to search for
       *
       * @return the DjVmDir.File
       */
      public File title_to_file( String title)
      {
        return (File)title2file[title];
      }

      //~ Inner Classes ----------------------------------------------------------

      /**
       * This class stores the details about a single page of include file within
       * a document.
       *
       * @author Bill C. Riemers
       * @version $Revision: 1.4 $
       */
      public class File
      {
        //~ Static fields/initializers -------------------------------------------

        /** flag to indicate an include file */
        public const  byte INCLUDE = 0;

        /** flag to indicate a page */
        public const byte PAGE = 1;

        /** flag to indicate thumbnails */
        public const  byte THUMBNAILS = 2;

        /** flag to indicate shared annotations */
        public const byte SHARED_ANNO = 3;

        public const  byte IS_PAGE_0 = 1;
        public const byte HAS_NAME_0 = 2;
        public const byte HAS_TITLE_0 = 4;
        public const byte HAS_NAME = (byte)0x80;
        public const byte HAS_TITLE = 0x40;
        public const  byte TYPE_MASK = 0x3f;

        //~ Instance fields ------------------------------------------------------

        /** The position of this file in the main document. */
        public int offset = 0;

        /** The size of this file. */
        public int size = 0;

        /** The id, or load name of this file. */
        public String id = null;

        /** The name, or save name of this file. */
        public String name = null;

        /** The title of this page. */
        public String title = null;

        /** flags */
        public byte flags = 0;

        /** The page number represented by this file. */
        public int page_num = (-1);

        //~ Constructors ---------------------------------------------------------

        /**
         * Creates a new File object.
         */
        public File() {}

        /**
         * Creates a new File object.
         *
         * @param load_name the load name (id) of this file
         * @param save_name the save name (name) of this file
         * @param title the title of this file
         * @param file_type the type of file
         */
        public File(
           String load_name,
           String save_name,
           String title,
           int    file_type)
        {
          set_load_name(load_name);
          set_save_name(save_name);
          set_title(title);
          flags = (byte)(file_type & TYPE_MASK);
        }

        //~ Methods --------------------------------------------------------------

        /**
         * Create a clone of this object
         *
         * @return the newly created clone
         */
        public Object clone()
        {
          File retval = null;


          retval = new File(this.get_load_name(), get_save_name(), get_title(), flags);
          

          return retval;
        }

        /**
         * Query the load name (id) of this file.
         *
         * @return the load name
         */
        public  String get_load_name()
        {
          return id;
        }

        /**
         * Query the save name (name) of this file.
         *
         * @return the save name
         */
        public  String get_save_name()
        {
          return ((name != null) && (name.Length > 0))
          ? name
          : id;
        }

        /**
         * Query the title of this file.
         *
         * @return the title
         */
        public  String get_title()
        {
          return ((title != null) && (title.Length > 0))
          ? title
          : id;
        }

        /**
         * Query the page number of this file.
         *
         * @return page number or -1
         */
        public  int get_page_num()
        {
          return page_num;
        }

        /**
         * Query a description of the file type.
         * 
         * @return a String describing the file type. 
         *
         * @throws IOException if an error occurs
         */
        public String get_str_type()
        {
          String type;

          switch(flags & TYPE_MASK)
          {
            case INCLUDE :
              type = "INCLUDE";

              break;
            case PAGE :
              type = "PAGE";

              break;
            case THUMBNAILS :
              type = "THUMBNAILS";

              break;
            case SHARED_ANNO :
              type = "SHARED_ANNO";

              break;
            default :
              throw new IOException("DjVmDir.get_str_type");
          }

          return type;
        }

        /**
         * Query if this is an include file.
         *
         * @return true if this is an include file
         */
        public bool is_include()
        {
          return (flags & TYPE_MASK) == INCLUDE;
        }

        /**
         * Query if this is a page.
         *
         * @return true if this is a page
         */
        public bool is_page()
        {
          return (flags & TYPE_MASK) == PAGE;
        }

        /**
         * Query if this is a shared annotation
         *
         * @return true if this is a shared annotation
         */
        public bool is_shared_anno()
        {
          return (flags & TYPE_MASK) == SHARED_ANNO;
        }

        /**
         * Query if this is a thumbnail.
         *
         * @return true if this is a thumbnail
         */
        public bool is_thumbnails()
        {
          return (flags & TYPE_MASK) == THUMBNAILS;
        }

        /**
         * Set the load name (id) of this file.
         *
         * @param id the load name to use
         */
        public void set_load_name( String id)
        {
          int k = id.LastIndexOf('/');
          this.id = (k != -1)
            ? id.Substring(k)
            : id;
        }

        /**
         * Set the title of this page.
         *
         * @param title the page title
         */
        public void set_title( String title)
        {
          this.title = title;
        }

        /**
         * Set the save name (name) of this file.
         *
         * @param name save name to use
         */
        protected void set_save_name( String name)
        {
          int k = name.LastIndexOf('/');
          this.name = (k != -1)
            ? name.Substring(k)
            : name;
        }
      }
    }


}
